﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
//using wottools;

namespace Packed_Section_Reader
{
  public class Packed_Section
  {
    public static readonly Int32 Packed_Header = 0x62a14e45;
    public static readonly char[] intToBase64 = new char[] {
          'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
          'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
          'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
    public const int MAX_LENGTH = 256;

    public class DataDescriptor
    {
      public readonly int address;
      public readonly int end;
      public readonly int type;

      public DataDescriptor(int end, int type, int address)
      {
        this.end = end;
        this.type = type;
        this.address = address;
      }

      public override string ToString()
      {
        StringBuilder sb = new StringBuilder("[");
        sb.Append("0x");
        sb.Append(Convert.ToString(end, 16));
        sb.Append(", ");
        sb.Append("0x");
        sb.Append(Convert.ToString(type, 16));
        sb.Append("]@0x");
        sb.Append(Convert.ToString(address, 16));
        return sb.ToString();
      }
    }

    public class ElementDescriptor
    {
      public readonly int nameIndex;
      public readonly DataDescriptor dataDescriptor;

      public ElementDescriptor(int nameIndex, DataDescriptor dataDescriptor)
      {
        this.nameIndex = nameIndex;
        this.dataDescriptor = dataDescriptor;
      }

      public override string ToString()
      {
        StringBuilder sb = new StringBuilder("[");
        sb.Append("0x");
        sb.Append(Convert.ToString(nameIndex, 16));
        sb.Append(":");
        sb.Append(dataDescriptor);
        return sb.ToString();
      }
    }

    public string readStringTillZero(BinaryReader reader)
    {
      char[] work = new char[MAX_LENGTH];

      int i = 0;

      char c = reader.ReadChar();
      while (c != Convert.ToChar(0x00))
      {
        work[i++] = c;
        c = reader.ReadChar();
      }
      return new string(work, 0, i);

    }

    public List<string> readDictionary(BinaryReader reader)
    {
      List<string> dictionary = new List<string>();
      int counter = 0;
      string text = readStringTillZero(reader);

      while (!(text.Length == 0))
      {
        dictionary.Add(text);
        text = readStringTillZero(reader);
        counter++;
      }
      return dictionary;
    }

    public int readLittleEndianShort(BinaryReader reader)
    {
      int LittleEndianShort = reader.ReadInt16();
      return LittleEndianShort;
    }

    public int readLittleEndianInt(BinaryReader reader)
    {
      int LittleEndianInt = reader.ReadInt32();
      return LittleEndianInt;
    }

    public DataDescriptor readDataDescriptor(BinaryReader reader)
    {
      int selfEndAndType = readLittleEndianInt(reader);
      return new DataDescriptor(selfEndAndType & 0x0fffffff, selfEndAndType >> 28, (int)reader.BaseStream.Position);
    }

    public ElementDescriptor[] readElementDescriptors(BinaryReader reader, int number)
    {
      ElementDescriptor[] elements = new ElementDescriptor[number];
      for (int i = 0; i < number; i++)
      {
        int nameIndex = readLittleEndianShort(reader);
        DataDescriptor dataDescriptor = readDataDescriptor(reader);
        elements[i] = new ElementDescriptor(nameIndex, dataDescriptor);
      }
      return elements;
    }

    public string readString(BinaryReader reader, int lengthInBytes)
    {
      string rString = new string(reader.ReadChars(lengthInBytes), 0, lengthInBytes);

      return rString;
    }

    public string readNumber(BinaryReader reader, int lengthInBytes)
    {
      string Number = "";
      switch (lengthInBytes)
      {
        case 1:
          Number = Convert.ToString(reader.ReadSByte());
          break;
        case 2:
          Number = Convert.ToString(readLittleEndianShort(reader));
          break;
        case 4:
          Number = Convert.ToString(readLittleEndianInt(reader));
          break;
        default:
          Number = "0";
          break;
      }
      return Number;

    }

    public float readLittleEndianFloat(BinaryReader reader)
    {
      float LittleEndianFloat = reader.ReadSingle();
      return LittleEndianFloat;
    }

    public string readFloats(BinaryReader reader, int lengthInBytes)
    {
      int n = lengthInBytes / 4;

      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < n; i++)
      {

        if (i != 0)
        {
          sb.Append(" ");
        }
        float rFloat = readLittleEndianFloat(reader);
        sb.Append(rFloat.ToString("0.000000"));
      }
      return sb.ToString();
    }


    public bool readBoolean(BinaryReader reader, int lengthInBytes)
    {
      bool @bool = lengthInBytes == 1;
      if (@bool)
      {
        if (reader.ReadSByte() != 1)
        {
          throw new ArgumentException("Boolean error");
        }
      }

      return @bool;
    }

    private static string byteArrayToBase64(sbyte[] a)
    {
      int aLen = a.Length;
      int numFullGroups = aLen / 3;
      int numBytesInPartialGroup = aLen - 3 * numFullGroups;
      int resultLen = 4 * ((aLen + 2) / 3);
      StringBuilder result = new StringBuilder(resultLen);

      int inCursor = 0;
      for (int i = 0; i < numFullGroups; i++)
      {
        int byte0 = a[inCursor++] & 0xff;
        int byte1 = a[inCursor++] & 0xff;
        int byte2 = a[inCursor++] & 0xff;
        result.Append(intToBase64[byte0 >> 2]);
        result.Append(intToBase64[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
        result.Append(intToBase64[(byte1 << 2) & 0x3f | (byte2 >> 6)]);
        result.Append(intToBase64[byte2 & 0x3f]);
      }

      if (numBytesInPartialGroup != 0)
      {
        int byte0 = a[inCursor++] & 0xff;
        result.Append(intToBase64[byte0 >> 2]);
        if (numBytesInPartialGroup == 1)
        {
          result.Append(intToBase64[(byte0 << 4) & 0x3f]);
          result.Append("==");
        }
        else
        {
          int byte1 = a[inCursor++] & 0xff;
          result.Append(intToBase64[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
          result.Append(intToBase64[(byte1 << 2) & 0x3f]);
          result.Append('=');
        }
      }

      return result.ToString();
    }

    public string readBase64(BinaryReader reader, int lengthInBytes)
    {
      sbyte[] bytes = new sbyte[lengthInBytes];
      for (int i = 0; i < lengthInBytes; i++)
      {
        bytes[i] = reader.ReadSByte();
      }
      return byteArrayToBase64(bytes);
    }

    public string readAndToHex(BinaryReader reader, int lengthInBytes)
    {
      sbyte[] bytes = new sbyte[lengthInBytes];
      for (int i = 0; i < lengthInBytes; i++)
      {
        bytes[i] = reader.ReadSByte();
      }
      StringBuilder sb = new StringBuilder("[ ");
      foreach (byte b in bytes)
      {
        sb.Append(Convert.ToString((b & 0xff), 16));
        sb.Append(" ");
      }
      sb.Append("]L:");
      sb.Append(lengthInBytes);

      return sb.ToString();
    }

    public int readData(BinaryReader reader, List<string> dictionary, XmlNode element, XmlDocument xDoc, int offset, DataDescriptor dataDescriptor)
    {
      int lengthInBytes = dataDescriptor.end - offset;
      if (dataDescriptor.type == 0x0)
      {
        // Element                
        readElement(reader, element, xDoc, dictionary);
      }
      else if (dataDescriptor.type == 0x1)
      {
        // String
        element.InnerText = readString(reader, lengthInBytes);

      }
      else if (dataDescriptor.type == 0x2)
      {
        // Integer number
        element.InnerText = String.Format("\t{0}\t", readNumber(reader, lengthInBytes));
      }
      else if (dataDescriptor.type == 0x3)
      {
        // Floats
        string str = readFloats(reader, lengthInBytes);

        string[] strData = str.Split(' ');
        if (strData.Length == 12)
        {
          XmlNode row0 = xDoc.CreateElement("row0");
          XmlNode row1 = xDoc.CreateElement("row1");
          XmlNode row2 = xDoc.CreateElement("row2");
          XmlNode row3 = xDoc.CreateElement("row3");
          row0.InnerText = String.Format("\t{0} {1} {2}\t", strData[0], strData[1], strData[2]);
          row1.InnerText = String.Format("\t{0} {1} {2}\t", strData[3], strData[4], strData[5]);
          row2.InnerText = String.Format("\t{0} {1} {2}\t", strData[6], strData[7], strData[8]);
          row3.InnerText = String.Format("\t{0} {1} {2}\t", strData[9], strData[10], strData[11]);
          element.AppendChild(row0);
          element.AppendChild(row1);
          element.AppendChild(row2);
          element.AppendChild(row3);
        }
        else
        {
          element.InnerText = String.Format("\t{0}\t", str);
        }
      }
      else if (dataDescriptor.type == 0x4)
      {
        // Boolean
        element.InnerText = readBoolean(reader, lengthInBytes) ? "\ttrue\t" : "\tfalse\t";
      }
      else if (dataDescriptor.type == 0x5)
      {
        // Base64
        element.InnerText = String.Format("\t{0}\t", readBase64(reader, lengthInBytes));
      }
      else
      {
        throw new ArgumentException(String.Format("Unknown type of \"{0}: {1} {2}",
          element.Name, dataDescriptor, readAndToHex(reader, lengthInBytes)));
      }

      return dataDescriptor.end;
    }

    public void readElement(BinaryReader reader, XmlNode element, XmlDocument xDoc, List<string> dictionary)
    {
      int childrenNmber = readLittleEndianShort(reader);
      DataDescriptor selfDataDescriptor = readDataDescriptor(reader);
      ElementDescriptor[] children = readElementDescriptors(reader, childrenNmber);

      int offset = readData(reader, dictionary, element, xDoc, 0, selfDataDescriptor);

      foreach (ElementDescriptor elementDescriptor in children)
      {
        XmlNode child = xDoc.CreateElement(dictionary[elementDescriptor.nameIndex]);
        offset = readData(reader, dictionary, child, xDoc, offset, elementDescriptor.dataDescriptor);
        element.AppendChild(child);
      }

    }
  }
}

